package com.guanyu.u2cache.register;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.guanyu.u2cache.annotation.U2Cacheable;
import com.guanyu.u2cache.cache.CacheOperate;
import com.guanyu.u2cache.caffeine.U2CacheManager;
import com.guanyu.u2cache.confinuelife.CachedInvocation;
import com.guanyu.u2cache.enums.CacheType;
import com.guanyu.u2cache.enums.EventType;
import com.guanyu.u2cache.exception.BusinessException;
import com.guanyu.u2cache.expression.CacheOperationExpressionEvaluator;
import com.guanyu.u2cache.expression.GeneratorTransfer;
import com.guanyu.u2cache.manage.ListenerEventManag;
import com.guanyu.u2cache.model.EventData;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author zhouyunjian
 * @date 2023/10/27 17:09
 **/
@Slf4j
public class U2CacheRegister implements ApplicationContextAware, CommandLineRunner {
    @Resource
    private CacheOperate cacheOperate;
    private GenericApplicationContext act;
    private final GeneratorTransfer transfer = new GeneratorTransfer();
    private KeyGenerator keyGenerator = new SimpleKeyGenerator();

    /**
     * @param act
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext act) throws BeansException {
        this.act = (GenericApplicationContext) act;
    }

    /**
     * 本地服务
     ***/
    private Thread thread;

    /**
     * 初始化后加载
     *
     * @param args
     */
    @SneakyThrows
    @Override
    public void run(String... args) {
        cacheEventRegister();
        cacheManageRegister();
        preHeatData();
//        thread = new Thread(this, U2CacheRegister.class + "-" + new SecureRandom().nextInt(100));
//        thread.setDaemon(true);
//        thread.start();
    }

    /**
     * 数据预热：
     * 1、拿到缓存注释类
     * 2、注册缓存事件-名称绑定信息
     */
    @SneakyThrows
    private void cacheEventRegister() {
        log.debug("缓存事件-名称绑定开始");
        List<Object> beans = CollectionUtil.unionAll(act.getBeansWithAnnotation(Component.class).values(), act.getBeansWithAnnotation(Service.class).values());
        beans.forEach(value -> {
            Class<?> clazz = AopUtils.getTargetClass(value);
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                if (method.isAnnotationPresent(U2Cacheable.class)) {
                    U2Cacheable cacheable = AnnotationUtils.getAnnotation(method, U2Cacheable.class);
                    try {
                        String[] eventNames = cacheable.listenEventName();
                        if (ObjectUtil.isEmpty(eventNames)) {
                            continue;
                        }
                        for (String eventName : eventNames) {
                            ListenerEventManag.getSingleton().registerEvent(eventName, cacheable.cacheNames());
                        }
                    } catch (Exception e) {
                        log.error("缓存事件-名称绑定[{}]名加载信息[{}]", method.getName(), e.getMessage(), e);
                    }
                }
            }
        });
        log.debug("缓存事件-名称绑定结束");
    }

    /**
     * 数据预热：
     * 1、拿到缓存注释类
     * 2、初始化加载缓存类的事件信息
     */
    @SneakyThrows
    private void cacheManageRegister() {
        log.debug("缓存类名加载开始");
        List<Object> beans = CollectionUtil.unionAll(act.getBeansWithAnnotation(Component.class).values(), act.getBeansWithAnnotation(Service.class).values());
        beans.forEach(value -> {
            Class<?> clazz = AopUtils.getTargetClass(value);
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                if (method.isAnnotationPresent(U2Cacheable.class)) {
                    U2Cacheable cacheable = AnnotationUtils.getAnnotation(method, U2Cacheable.class);
                    try {
                        for (String cacheName : cacheable.cacheNames()) {
                            if (U2CacheManager.getSingleton().isExist(cacheName)) {
                                continue;
                            }
                            log.debug("注册一级缓存[{}]", cacheName);
                            U2CacheManager.getSingleton().registerCustomCache(cacheName);
                        }
                    } catch (Exception e) {
                        log.error("缓存类名[{}]名加载信息[{}]", method.getName(), e.getMessage(), e);
                    }
                }
            }
        });
        log.debug("缓存类名加载结束");
    }

    /**
     * 数据预热：
     * 1、拿到注释类
     * 2、判定是否预热
     * 3、执行其所在方法
     */
    @SneakyThrows
    private void preHeatData() {
        log.debug("缓存预热加载开始");
        List<Object> beans = CollectionUtil.unionAll(act.getBeansWithAnnotation(Component.class).values(), act.getBeansWithAnnotation(Service.class).values());
        beans.forEach(value -> {
            Class<?> clazz = AopUtils.getTargetClass(value);
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                if (method.isAnnotationPresent(U2Cacheable.class)) {
                    U2Cacheable cacheable = AnnotationUtils.getAnnotation(method, U2Cacheable.class);
                    if (!cacheable.preHeat()) {
                        continue;
                    }
                    if (method.getParameterCount() > 0) {
                        throw new BusinessException(String.format("标注预热的类[{}]方法[{}]下不允许有参数", clazz.getName(), method.getName()));
                    }
                    try {
                        Parameter[] args = method.getParameters();
                        Object target = value;
                        Object result = method.invoke(target, args);
                        Class returnType = method.getReturnType();
                        String[] eventNames = cacheable.cacheNames();
                        CacheType cacheType = cacheable.cacheType();
                        long expireTime = cacheable.expireTime();
                        TimeUnit timeUnit = cacheable.expireUnit();
                        String key = transfer.generateKey(new CacheOperationExpressionEvaluator(), keyGenerator, cacheable.key(), method, args, target).toString();
                        CachedInvocation cachedInvocation = CachedInvocation.builder()
                                .arguments(args)
                                .targetBean(target)
                                .targetMethod(method)
                                .returnType(returnType)
                                .metaData(EventData.builder()
                                        .cacheNames(eventNames)
                                        .key(key)
                                        .expiredTime(expireTime)
                                        .expireUnit(timeUnit)
                                        .eventType(EventType.load)
                                        .cacheType(cacheType)
                                        .build()
                                )
                                .build();
                        if (cacheable.continueLife() && cacheOperate.isExistKey(eventNames, key, cacheType)) {
                            act.publishEvent(cachedInvocation);
                        }
                    } catch (Exception e) {
                        log.error("缓存[{}]预热异常e[{}]", clazz.getSimpleName(), method.getName(), e.getMessage(), e);
                    }
                }
            }
        });
        log.debug("缓存预热加载结束");
    }

//    @SneakyThrows
//    @Override
//    public void run() {
//        this.preHeatData();
//    }
}
